// -------------------------------------------------
// BlinKit - BlinKit Library
// -------------------------------------------------
//   File Name: web_page_popup.cpp
// Description: WebPagePopup Class
//      Author: Ziming Li
//     Created: 2022-04-26
// -------------------------------------------------
// Copyright (C) 2022 MingYang Software Technology.
// -------------------------------------------------

/*
 * Copyright (C) 2012 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "./web_page_popup.h"

#include "blinkit/blink/public/platform/web_layer_tree_view.h"
#include "blinkit/blink/renderer/bindings/core/script_controller.h"
// BKTODO: #include "core/dom/ContextFeatures.h"
#include "blinkit/blink/renderer/core/events/message_event.h"
#include "blinkit/blink/renderer/core/frame/FrameHost.h"
#include "blinkit/blink/renderer/core/frame/FrameView.h"
#if 0 // BKTODO:
#include "core/frame/Settings.h"
#endif
#include "blinkit/blink/renderer/core/frame/VisualViewport.h"
#if 0 // BKTODO:
#include "core/input/EventHandler.h"
#include "core/layout/LayoutView.h"
#endif
#include "blinkit/blink/renderer/core/loader/FrameLoadRequest.h"
// BKTODO: #include "core/page/DOMWindowPagePopup.h"
#include "blinkit/blink/renderer/core/page/chrome_client.h"
#include "blinkit/blink/renderer/core/page/context_menu_client.h"
#include "blinkit/blink/renderer/core/page/drag_client.h"
#include "blinkit/blink/renderer/core/page/editor_client.h"
#include "blinkit/blink/renderer/core/page/focus_controller.h"
#include "blinkit/blink/renderer/core/page/page.h"
#include "blinkit/blink/renderer/core/page/page_popup_client.h"
#include "blinkit/blink/renderer/core/page/page_popup_controller.h"
#if 0 // BKTODO:
#include "modules/accessibility/AXObject.h"
#include "modules/accessibility/AXObjectCacheImpl.h"
#endif
#include "blinkit/blink/renderer/platform/EventDispatchForbiddenScope.h"
#include "blinkit/blink/renderer/platform/ScriptForbiddenScope.h"
#if 0 // BKTODO:
#include "platform/TraceEvent.h"
#include "platform/heap/Handle.h"
#include "public/platform/WebCompositeAndReadbackAsyncCallback.h"
#include "public/platform/WebCursorInfo.h"
#include "public/web/WebAXObject.h"
#include "public/web/WebFrameClient.h"
#endif
#include "blinkit/blink/renderer/web/WebInputEventConversion.h"
#if 0 // BKTODO:
#include "web/WebLocalFrameImpl.h"
#include "web/WebSettingsImpl.h"
#endif
#include "blinkit/ui/web_page_host.h"
#include "blinkit/ui/web_view_impl.h"

namespace blink {

class PagePopupChromeClient final : public ChromeClient
{
public:
    explicit PagePopupChromeClient(WebPagePopup *popup) : m_popup(popup)
    {
        ASSERT(nullptr != m_popup->Host());
    }

    void setWindowRect(const IntRect& rect) override
    {
        m_popup->setWindowRect(rect);
    }
private:
    void closeWindowSoon() override
    {
        m_popup->closePopup();
    }

    IntRect windowRect() override
    {
        return m_popup->m_windowRectInScreen;
    }

    IntRect viewportToScreen(const IntRect& rect) const override
    {
        IntRect rectInScreen(rect);
        m_popup->ConvertViewportToWindow(&rectInScreen);
        rectInScreen.move(m_popup->m_windowRectInScreen.x(), m_popup->m_windowRectInScreen.y());
        return rectInScreen;
    }

#if 0 // BKTODO:
    void addMessageToConsole(LocalFrame*, MessageSource, MessageLevel, const String& message, unsigned lineNumber, const String&, const String&) override
    {
#ifndef NDEBUG
        fprintf(stderr, "CONSOLE MESSSAGE:%u: %s\n", lineNumber, message.utf8().data());
#endif
    }
#endif

    void invalidateRect(const IntRect& paintRect) override
    {
        ASSERT(false); // BKTODO:
#if 0
        if (!paintRect.isEmpty())
            m_popup->widgetClient()->didInvalidateRect(paintRect);
#endif
    }

    void scheduleAnimation(Widget *) override
    {
        m_popup->ScheduleAnimation();
    }

#if 0 // BKTODO:
    void attachCompositorAnimationTimeline(WebCompositorAnimationTimeline* timeline, LocalFrame*) override
    {
        if (m_popup->m_layerTreeView)
            m_popup->m_layerTreeView->attachCompositorAnimationTimeline(timeline);
    }

    void detachCompositorAnimationTimeline(WebCompositorAnimationTimeline* timeline, LocalFrame*) override
    {
        if (m_popup->m_layerTreeView)
            m_popup->m_layerTreeView->detachCompositorAnimationTimeline(timeline);
    }
#endif

    WebScreenInfo screenInfo() const override
    {
        return m_popup->m_webView->Host()->GetScreenInfo();
    }

    void* webView() const override
    {
        return m_popup->m_webView;
    }

#if 0 // BKTODO:
    IntSize minimumWindowSize() const override
    {
        return IntSize(0, 0);
    }
#endif

    void setCursor(const Cursor& cursor, LocalFrame* localRoot) override
    {
        m_popup->m_webView->Host()->UpdateCursor(WebCursorInfo(cursor));
    }

    void needTouchEvents(bool needsTouchEvents) override
    {
        ASSERT(false); // BKTODO: m_popup->widgetClient()->hasTouchEventHandlers(needsTouchEvents);
    }

    GraphicsLayerFactory* graphicsLayerFactory() const override
    {
        return m_popup->m_webView->graphicsLayerFactory();
    }

    void attachRootGraphicsLayer(GraphicsLayer* graphicsLayer, LocalFrame* localRoot) override
    {
        m_popup->setRootGraphicsLayer(graphicsLayer);
    }

#if 0 // BKTODO:
    void postAccessibilityNotification(AXObject* obj, AXObjectCache::AXNotification notification) override
    {
        WebLocalFrameImpl* frame = WebLocalFrameImpl::fromFrame(m_popup->m_popupClient->ownerElement().document().frame());
        if (obj && frame && frame->client())
            frame->client()->postAccessibilityEvent(WebAXObject(obj), static_cast<WebAXEvent>(notification));
    }
#endif

    void setToolTip(const String& tooltipText, TextDirection dir, unsigned long cookie) override
    {
        m_popup->Host()->SetToolTipText(tooltipText, dir, cookie);
    }

    WebPagePopup *m_popup;
};

#if 0 // BKTODO:
class PagePopupFeaturesClient : public ContextFeaturesClient {
    bool isEnabled(Document*, ContextFeatures::FeatureType, bool) override;
};

bool PagePopupFeaturesClient::isEnabled(Document*, ContextFeatures::FeatureType type, bool defaultValue)
{
    if (type == ContextFeatures::PagePopup)
        return true;
    return defaultValue;
}
#endif

// WebPagePopup ----------------------------------------------------------------

static void InitializePageClients(PageClients &dst)
{
    static ContextMenuClient s_contextMenuClient;
    dst.contextMenuClient = &s_contextMenuClient;

    static EditorClient s_editorClient;
    dst.editorClient = &s_editorClient;

    static DragClient s_dragClient;
    dst.dragClient = &s_dragClient;
}

WebPagePopup::WebPagePopup(void) : m_host(WebPageHost::CreateForPagePopup(*this))
{
    WebPage::SetHost(m_host.get());
}

WebPagePopup::~WebPagePopup()
{
    ASSERT(!HasPage());
}

bool WebPagePopup::initialize(WebViewImpl *webView, PagePopupClient *popupClient)
{
    ASSERT(nullptr != webView);
    ASSERT(nullptr != popupClient);
    m_webView = webView;
    m_popupClient = popupClient;

    if (!initializePage())
        return false;

    Host()->Show();
    SetFocus(true);

    return true;
}

bool WebPagePopup::initializePage()
{
    PageClients pageClients;
    InitializePageClients(pageClients);
    m_chromeClient = std::make_unique<PagePopupChromeClient>(this);
    pageClients.chromeClient = m_chromeClient.get();
    WebPage::Initialize(pageClients, m_popupClient->zoomFactor());

#if 0 // BKTODO: Check the settings.
    m_page->settings().setScriptEnabled(true);
    m_page->settings().setAllowScriptsToCloseWindows(true);
    m_page->settings().setDeviceSupportsTouch(m_webView->page()->settings().deviceSupportsTouch());
    // FIXME: Should we support enabling a11y while a popup is shown?
    m_page->settings().setAccessibilityEnabled(m_webView->page()->settings().accessibilityEnabled());
    m_page->settings().setScrollAnimatorEnabled(m_webView->page()->settings().scrollAnimatorEnabled());
#endif

    LocalFrame *frame = GetFrame();
    // BKTODO: provideContextFeaturesTo(*m_page, adoptPtr(new PagePopupFeaturesClient()));
    frame->view()->setTransparent(false);
#if 0 // BKTODO:
    if (AXObjectCache* cache = m_popupClient->ownerElement().document().existingAXObjectCache())
        cache->childrenChanged(&m_popupClient->ownerElement());
#endif

    ASSERT(nullptr != frame->localDOMWindow());
    m_controller = PagePopupController::create(*this, m_popupClient);
    frame->script().SetContextClient(this);
    // BKTODO: ASSERT(m_popupClient->ownerElement().document().existingAXObjectCache() == frame->document()->existingAXObjectCache());

    std::shared_ptr<SharedBuffer> data = SharedBuffer::create();
    m_popupClient->writeDocument(data.get());

    frame->loader().load(FrameLoadRequest(nullptr, blankURL(), SubstituteData(data, "text/html", "UTF-8", KURL(), ForceSynchronousLoad)));
    return true;
}

void WebPagePopup::postMessage(const String& message)
{
    if (!HasPage())
        return;
    ScriptForbiddenScope::AllowUserAgentScript allowScript;
    if (LocalDOMWindow *window = GetFrame()->localDOMWindow())
        window->dispatchEvent(MessageEvent::create(message));
}

void WebPagePopup::destroyPage()
{
    if (HasPage())
        WebPage::DestroyPage();
}

#if 0 // BKTODO:
AXObject* WebPagePopup::rootAXObject()
{
    if (!m_page || !m_page->mainFrame())
        return 0;
    Document* document = toLocalFrame(m_page->mainFrame())->document();
    if (!document)
        return 0;
    AXObjectCache* cache = document->axObjectCache();
    ASSERT(cache);
    return toAXObjectCacheImpl(cache)->getOrCreate(document->layoutView());
}
#endif

void WebPagePopup::setWindowRect(const IntRect& rectInScreen)
{
    m_windowRectInScreen = rectInScreen;
    Host()->SetWindowRect(m_windowRectInScreen);
}

void WebPagePopup::setRootGraphicsLayer(GraphicsLayer *layer)
{
    m_rootGraphicsLayer = layer;
    m_rootLayer = nullptr != layer ? layer->platformLayer() : nullptr;

    setIsAcceleratedCompositingActive(layer);
    if (nullptr != m_layerTreeView)
    {
        if (nullptr != m_rootLayer)
            m_layerTreeView->setRootLayer(*m_rootLayer);
        else
            m_layerTreeView->clearRootLayer();
    }
}

void WebPagePopup::setIsAcceleratedCompositingActive(bool enter)
{
    if (m_isAcceleratedCompositingActive == enter)
        return;

    if (!enter)
    {
        m_isAcceleratedCompositingActive = false;
    }
    else if (nullptr != m_layerTreeView)
    {
        m_isAcceleratedCompositingActive = true;
    }
    else
    {
        ASSERT(false); // BKTODO:
#if 0
        m_widgetClient->initializeLayerTreeView();
        m_layerTreeView = m_widgetClient->layerTreeView();
        if (nullptr != m_layerTreeView)
        {
            m_layerTreeView->setVisible(true);
            m_isAcceleratedCompositingActive = true;
            GetPage()->layerTreeViewInitialized(*m_layerTreeView);
        }
        else
        {
            m_isAcceleratedCompositingActive = false;
        }
#endif
    }
}

#if 0 // BKTODO:
void WebPagePopup::beginFrame(double lastFrameTimeMonotonic)
{
    if (!m_page)
        return;
    // FIXME: This should use lastFrameTimeMonotonic but doing so
    // breaks tests.
    PageWidgetDelegate::animate(*m_page, monotonicallyIncreasingTime());
}

void WebPagePopup::willCloseLayerTreeView()
{
    if (m_page && m_layerTreeView)
        m_page->willCloseLayerTreeView(*m_layerTreeView);

    setIsAcceleratedCompositingActive(false);
    m_layerTreeView = 0;
}

void WebPagePopup::updateAllLifecyclePhases()
{
    if (!m_page)
        return;
    PageWidgetDelegate::updateAllLifecyclePhases(*m_page, *m_page->deprecatedLocalMainFrame());
}

void WebPagePopup::paint(WebCanvas* canvas, const WebRect& rect)
{
    if (!m_closing)
        PageWidgetDelegate::paint(*m_page, canvas, rect, *m_page->deprecatedLocalMainFrame());
}

void WebPagePopup::resize(const WebSize& newSizeInViewport)
{
    WebRect newSize(0, 0, newSizeInViewport.width, newSizeInViewport.height);
    widgetClient()->convertViewportToWindow(&newSize);

    setWindowRect(WebRect(m_windowRectInScreen.x, m_windowRectInScreen.y, newSize.width, newSize.height));
    if (m_page) {
        toLocalFrame(m_page->mainFrame())->view()->resize(newSizeInViewport);
        m_page->frameHost().visualViewport().setSize(newSizeInViewport);
    }

    m_widgetClient->didInvalidateRect(WebRect(0, 0, newSize.width, newSize.height));
}
#endif

WebInputEventResult WebPagePopup::handleKeyEvent(const WebKeyboardEvent& event)
{
    return handleKeyEvent(PlatformKeyboardEventBuilder(event));
}

WebInputEventResult WebPagePopup::handleCharEvent(const WebKeyboardEvent& event)
{
    return handleKeyEvent(PlatformKeyboardEventBuilder(event));
}

#if 0 // BKTODO:
WebInputEventResult WebPagePopup::handleGestureEvent(const WebGestureEvent& event)
{
    if (m_closing || !m_page || !m_page->mainFrame() || !toLocalFrame(m_page->mainFrame())->view())
        return WebInputEventResult::NotHandled;
    if (event.type == WebInputEvent::GestureTap && !isViewportPointInWindow(event.x, event.y)) {
        cancel();
        return WebInputEventResult::NotHandled;
    }
    LocalFrame& frame = *toLocalFrame(m_page->mainFrame());
    return frame.eventHandler().handleGestureEvent(PlatformGestureEventBuilder(frame.view(), event));
}

void WebPagePopup::handleMouseDown(LocalFrame& mainFrame, const WebMouseEvent& event)
{
    if (isViewportPointInWindow(event.x, event.y))
        PageWidgetEventHandler::handleMouseDown(mainFrame, event);
    else
        cancel();
}

WebInputEventResult WebPagePopup::handleMouseWheel(LocalFrame& mainFrame, const WebMouseWheelEvent& event)
{
    if (isViewportPointInWindow(event.x, event.y))
        return PageWidgetEventHandler::handleMouseWheel(mainFrame, event);
    cancel();
    return WebInputEventResult::NotHandled;
}

bool WebPagePopup::isViewportPointInWindow(int x, int y)
{
    WebRect pointInWindow(x, y, 0, 0);
    widgetClient()->convertViewportToWindow(&pointInWindow);
    return IntRect(0, 0, m_windowRectInScreen.width, m_windowRectInScreen.height).contains(IntPoint(pointInWindow.x, pointInWindow.y));
}

WebInputEventResult WebPagePopup::handleInputEvent(const WebInputEvent& event)
{
    if (m_closing)
        return WebInputEventResult::NotHandled;
    return PageWidgetDelegate::handleInputEvent(*this, event, m_page->deprecatedLocalMainFrame());
}
#endif

WebInputEventResult WebPagePopup::handleKeyEvent(const PlatformKeyboardEvent& event)
{
    ASSERT(false); // BKTODO:
    return WebInputEventResult::NotHandled;
#if 0
    if (m_closing || !m_page->mainFrame() || !toLocalFrame(m_page->mainFrame())->view())
        return WebInputEventResult::NotHandled;
    return toLocalFrame(m_page->mainFrame())->eventHandler().keyEvent(event);
#endif
}

void WebPagePopup::SetFocus(bool enable)
{
    Page *page = GetPage();
    if (nullptr == page)
        return;

    page->focusController().setFocused(enable);
    if (enable)
        page->focusController().setActive(true);
}

#if 0 // BKTODO:
void WebPagePopup::close()
{
    m_closing = true;
    // In case closePopup() was not called.
    if (m_page) {
        destroyPage();
        m_popupClient->didClosePopup();
        m_webView->cleanupPagePopup();
    }
    m_widgetClient = 0;
    deref();
}
#endif

void WebPagePopup::closePopup()
{
    // This function can be called in EventDispatchForbiddenScope for the main
    // document, and the following operations dispatch some events.  It's safe
    // because web authors can't listen the events.
    EventDispatchForbiddenScope::AllowUserAgentEvents allowEvents;

    if (LocalFrame *frame = GetFrame())
        frame->loader().stopAllLoaders();
    m_closing = true;

    destroyPage();
    Host()->Close();

    m_popupClient->didClosePopup();
}

#if 0 // BKTODO:
LocalDOMWindow* WebPagePopup::window()
{
    return m_page->deprecatedLocalMainFrame()->localDOMWindow();
}

void WebPagePopup::layoutAndPaintAsync(WebLayoutAndPaintAsyncCallback* callback)
{
    m_layerTreeView->layoutAndPaintAsync(callback);
}

void WebPagePopup::compositeAndReadbackAsync(WebCompositeAndReadbackAsyncCallback* callback)
{
    ASSERT(isAcceleratedCompositingActive());
    m_layerTreeView->compositeAndReadbackAsync(callback);
}

WebPoint WebPagePopup::positionRelativeToOwner()
{
    WebRect windowRect = m_webView->client()->rootWindowRect();
    return WebPoint(m_windowRectInScreen.x - windowRect.x, m_windowRectInScreen.y - windowRect.y);
}
#endif

void WebPagePopup::cancel()
{
    if (m_popupClient)
        m_popupClient->closePopup();
}

void WebPagePopup::ContextReady(JSContext *ctx)
{
    JSValue window = JS_GetGlobalObject(ctx);
    JS_SetPropertyStr(ctx, window, "pagePopupController", m_controller->Wrap(ctx));
    JS_FreeValue(ctx, window);
}

std::shared_ptr<WebPagePopup> WebPagePopup::Create(void)
{
    return zed::wrap_shared(new WebPagePopup);
}

void WebPagePopup::OnFrameCreated(LocalFrame *frame)
{
    frame->setPagePopupOwner(m_popupClient->ownerElement());
    frame->setView(FrameView::create(frame));
}

void WebPagePopup::Resize(const IntSize &size)
{
    WebPage::Resize(size);
    GetFrame()->view()->resize(size);
    GetPage()->frameHost().visualViewport().setSize(size);
}

} // namespace blink
